ποΈGitΠ―ΡΠ°ποΈ
Commit 9b8ac6a4609c2174f06b50b0b0461c676120aa9b
Parents : 0c3ab92
Author : James Rich <2199651+jamesarich@users.noreply.github.com>
Signature : Signature validation error
Date : 2026-03-24T17:22:37-05:00
Committer : GitHub <noreply@github.com>
Date : 2026-03-24T22:22:37Z
build(desktop): enable ProGuard minification and tree-shaking (#4904)
Signed-off-by: James Rich <2199651+jamesarich@users.noreply.github.com>
Changes
3 files changed, 229 insertions(+), 12 deletions(-)
Diff
diff --git a/desktop/README.md b/desktop/README.md
index 5e177a5486..ea17d0eb79 100644
--- a/desktop/README.md
+++ b/desktop/README.md
@@ -16,10 +16,27 @@ A Compose Desktop application target β the first full non-Android target for t
# Run tests
./gradlew :desktop:test
-# Package native distribution (DMG/MSI/DEB)
+# Package native distribution (DMG/MSI/DEB) β debug (no ProGuard)
./gradlew :desktop:packageDistributionForCurrentOS
+
+# Package native distribution (DMG/MSI/DEB) β release (ProGuard minified)
+./gradlew :desktop:packageReleaseDistributionForCurrentOS
```
+## ProGuard / Minification
+
+Release builds use ProGuard for tree-shaking (unused code removal), significantly reducing distribution size. Obfuscation is disabled since the project is open-source.
+
+**Configuration:**
+- `build.gradle.kts` β `buildTypes.release.proguard` block enables ProGuard with `optimize.set(true)` and `obfuscate.set(false)`.
+- `proguard-rules.pro` β Comprehensive keep-rules for all reflection/JNI-sensitive dependencies (Koin, kotlinx-serialization, Wire protobuf, Room KMP, Ktor, Kable BLE, Coil, SQLite JNI, Compose Multiplatform resources).
+
+**Troubleshooting ProGuard issues:**
+- If the release build crashes at runtime with `ClassNotFoundException` or `NoSuchMethodError`, a library is loading classes via reflection that ProGuard stripped. Add a `-keep` rule in `proguard-rules.pro`.
+- To debug which classes ProGuard removes, temporarily add `-printusage proguard-usage.txt` to the rules file and inspect the output in `desktop/proguard-usage.txt`.
+- To see the full mapping of optimizations applied, add `-printseeds proguard-seeds.txt`.
+- Run `./gradlew :desktop:runRelease` for a quick smoke-test of the minified app before packaging.
+
## Architecture
The module depends on the JVM variants of KMP modules:
diff --git a/desktop/build.gradle.kts b/desktop/build.gradle.kts
index fe4e86c0c0..1ffb0f96a3 100644
--- a/desktop/build.gradle.kts
+++ b/desktop/build.gradle.kts
@@ -45,10 +45,9 @@ compose.desktop {
mainClass = "org.meshtastic.desktop.MainKt"
buildTypes.release.proguard {
- // Note: Enabling ProGuard will reduce final distribution size significantly,
- // but will require thorough testing of serialization, reflection (Koin), and JNI (SQLite).
- // Recommend enabling when ready: isEnabled.set(true)
- isEnabled.set(false)
+ isEnabled.set(true)
+ obfuscate.set(false) // Open-source project β obfuscation adds no value
+ optimize.set(true)
configurationFiles.from(project.file("proguard-rules.pro"))
}
diff --git a/desktop/proguard-rules.pro b/desktop/proguard-rules.pro
index 10a50ac4ea..a73c347d17 100644
--- a/desktop/proguard-rules.pro
+++ b/desktop/proguard-rules.pro
@@ -1,14 +1,215 @@
+# ============================================================================
+# Meshtastic Desktop β ProGuard rules for release minification
+# ============================================================================
+# Open-source project: we rely on tree-shaking (unused code removal) for size
+# reduction. Obfuscation is disabled in build.gradle.kts (obfuscate.set(false)).
+#
+# Key libraries requiring keep-rules (reflection, JNI, code generation):
+# Koin (DI via reflection), kotlinx-serialization (generated serializers),
+# Wire protobuf (ADAPTER reflection), Room KMP (generated DB + converters),
+# Ktor (Java engine + ServiceLoader), Kable BLE, Coil, Compose Multiplatform
+# resources, SQLite bundled (JNI), AboutLibraries.
+# ============================================================================
+
+# ---- General ----------------------------------------------------------------
+
+# Preserve line numbers for meaningful stack traces
+-keepattributes SourceFile,LineNumberTable,*Annotation*,Signature,InnerClasses,EnclosingMethod,Exceptions
+
+# Suppress notes about duplicate resource files (common in fat JARs)
+-dontnote **
+
+# Do not parse/rewrite Kotlin metadata during shrinking/optimization.
+# ProGuard's KotlinShrinker cannot handle the metadata produced by Compose
+# Multiplatform 1.11.x + Kotlin 2.3.x, causing a NullPointerException.
+# Since we disable obfuscation (class names remain stable), metadata references
+# stay valid and do not need rewriting. The annotations themselves are preserved
+# by -keepattributes *Annotation*.
+-dontprocesskotlinmetadata
+
+# ---- Entry point ------------------------------------------------------------
+
+-keep class org.meshtastic.desktop.MainKt { *; }
+
+# ---- Kotlin / Coroutines ---------------------------------------------------
+
+# Keep Kotlin metadata for reflection-dependent libraries
+-keep class kotlin.Metadata { *; }
+-keep class kotlin.reflect.** { *; }
+
+# Coroutines internals
+-dontwarn kotlinx.coroutines.**
+-keep class kotlinx.coroutines.** { *; }
+-keep class kotlin.coroutines.Continuation { *; }
+
+# ---- Koin DI (reflection-based injection) -----------------------------------
+
+# Koin core β uses reflection to instantiate definitions
+-keep class org.koin.** { *; }
+-dontwarn org.koin.**
+
+# Keep all Koin-annotated @Module / @ComponentScan classes and their generated
+# counterparts so Koin K2 plugin output survives tree-shaking.
+-keep @org.koin.core.annotation.Module class * { *; }
+-keep @org.koin.core.annotation.ComponentScan class * { *; }
+-keep @org.koin.core.annotation.Single class * { *; }
+-keep @org.koin.core.annotation.Factory class * { *; }
+
+# Generated Koin module extensions (K2 plugin output)
+-keep class org.meshtastic.**.di.** { *; }
+
+# ---- kotlinx-serialization --------------------------------------------------
+
+# The serialization plugin generates companion $serializer classes and
+# serializer() factory methods that are invoked reflectively.
+-keepattributes RuntimeVisibleAnnotations
+-keep class kotlinx.serialization.** { *; }
+-dontwarn kotlinx.serialization.**
+
+# Keep @Serializable classes and their generated serializers
+-keepclassmembers @kotlinx.serialization.Serializable class ** {
+ # Companion object that holds the serializer() factory
+ static ** Companion;
+ kotlinx.serialization.KSerializer serializer(...);
+}
+-keepclassmembers class **.$serializer { *; }
+-keep class **.$serializer { *; }
+-keepclasseswithmembers class ** {
+ kotlinx.serialization.KSerializer serializer(...);
+}
+
+# ---- Wire protobuf ----------------------------------------------------------
+
+# Wire generates ADAPTER companion objects accessed via reflection
+-keep class com.squareup.wire.** { *; }
+-dontwarn com.squareup.wire.**
+
+# All generated proto message classes
+-keep class org.meshtastic.proto.** { *; }
+-keep class meshtastic.** { *; }
+
+# Suppress warnings about missing Android Parcelable (Wire cross-platform stubs)
-dontwarn android.os.Parcel**
-dontwarn android.os.Parcelable**
--dontwarn com.squareup.wire.AndroidMessage**
+
+# ---- Room KMP ---------------------------------------------------------------
+
+# Preserve generated database constructors (required for Room's reflective init)
+-keep class * extends androidx.room3.RoomDatabase { <init>(); }
+-keep class * implements androidx.room3.RoomDatabaseConstructor { *; }
+
+# Keep the expect/actual MeshtasticDatabaseConstructor
+-keep class org.meshtastic.core.database.MeshtasticDatabaseConstructor { *; }
+-keep class org.meshtastic.core.database.MeshtasticDatabase { *; }
+
+# Room DAOs β Room generates implementations at compile time; keep interfaces
+-keep class org.meshtastic.core.database.dao.** { *; }
+
+# Room Entities β accessed via reflection for column mapping
+-keep class org.meshtastic.core.database.entity.** { *; }
+
+# Room TypeConverters β invoked reflectively
+-keep class org.meshtastic.core.database.Converters { *; }
+
+# Room generated _Impl classes
+-keep class **_Impl { *; }
+
+# ---- SQLite bundled (JNI) ---------------------------------------------------
+
+-keep class androidx.sqlite.** { *; }
+-dontwarn androidx.sqlite.**
+
+# ---- Ktor (Java engine + ServiceLoader + content negotiation) ---------------
+
+# Ktor uses ServiceLoader and reflection for engine/plugin discovery
+-keep class io.ktor.** { *; }
-dontwarn io.ktor.**
-# Room KMP: preserve generated database constructor (required for R8/ProGuard)
--keep class * extends androidx.room.RoomDatabase { <init>(); }
+# Keep ServiceLoader metadata files
+-keepclassmembers class * implements io.ktor.client.HttpClientEngineFactory { *; }
-# Suppress ProGuard notes about duplicate resource files (common in Compose Desktop)
--dontnote **
+# Java HTTP client engine
+-keep class io.ktor.client.engine.java.** { *; }
+
+# ---- Coil (image loading) ---------------------------------------------------
+
+-keep class coil3.** { *; }
+-dontwarn coil3.**
+
+# ---- Kable BLE --------------------------------------------------------------
+
+-keep class com.juul.kable.** { *; }
+-dontwarn com.juul.kable.**
+
+# ---- Compose Multiplatform resources ----------------------------------------
+
+# Generated resource accessor classes (Res.string.*, Res.drawable.*, etc.)
+-keep class org.jetbrains.compose.resources.** { *; }
+-keep class org.meshtastic.core.resources.** { *; }
+
+# ---- AboutLibraries ---------------------------------------------------------
+
+-keep class com.mikepenz.aboutlibraries.** { *; }
+-dontwarn com.mikepenz.aboutlibraries.**
+
+# ---- Multiplatform Markdown Renderer ----------------------------------------
+
+-keep class com.mikepenz.markdown.** { *; }
+-dontwarn com.mikepenz.markdown.**
+
+# ---- QR Code Kotlin ---------------------------------------------------------
+
+-keep class io.github.g0dkar.qrcode.** { *; }
+-dontwarn io.github.g0dkar.qrcode.**
+-keep class qrcode.** { *; }
+-dontwarn qrcode.**
+
+# ---- Kermit logging ----------------------------------------------------------
+
+-keep class co.touchlab.kermit.** { *; }
+-dontwarn co.touchlab.kermit.**
+
+# ---- Okio -------------------------------------------------------------------
+
+-dontwarn okio.**
+-keep class okio.** { *; }
+
+# ---- DataStore --------------------------------------------------------------
+
+-keep class androidx.datastore.** { *; }
+-dontwarn androidx.datastore.**
+
+# ---- Paging -----------------------------------------------------------------
+
+-keep class androidx.paging.** { *; }
+-dontwarn androidx.paging.**
+
+# ---- Lifecycle / Navigation / ViewModel (JetBrains forks) -------------------
+
+-keep class androidx.lifecycle.** { *; }
+-keep class androidx.navigation3.** { *; }
+-dontwarn androidx.lifecycle.**
+-dontwarn androidx.navigation3.**
+
+# ---- Meshtastic application code --------------------------------------------
+
+# Keep all desktop module classes (thin host shell β not worth tree-shaking)
+-keep class org.meshtastic.desktop.** { *; }
+
+# Core model classes (used in serialization, Room, and Koin injection)
+-keep class org.meshtastic.core.model.** { *; }
+
+# ---- JVM runtime suppression ------------------------------------------------
-# Suppress specific reflection warnings that are safe to ignore
-dontwarn java.lang.reflect.**
--dontwarn sun.misc.Unsafe
\ No newline at end of file
+-dontwarn sun.misc.Unsafe
+-dontwarn java.lang.invoke.**
+
+# ---- jSerialComm (cross-platform serial library with Android stubs) ---------
+
+-dontwarn com.fazecast.jSerialComm.android.**
+
+# ---- Kotlin stdlib atomics (Kotlin 2.3+ intrinsics, not on JDK 17) ----------
+
+-dontwarn kotlin.concurrent.atomics.**
+-dontwarn kotlin.uuid.UuidV7Generator
Served by rngit 1.5.0 - Generated in 0.07s